package com.ceair.controller;

import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.ShearCaptcha;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.ceair.entity.model.Result;
import com.ceair.entity.model.response.CaptchaResult;
import com.ceair.entity.model.response.Oauth2UserinfoResult;
import com.ceair.service.IOauth2BasicUserService;
import com.ceair.support.RedisOperator;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.security.Principal;
import java.util.*;

import static com.ceair.constant.RedisConstants.*;

/**
 * @author wangbaohai
 * @ClassName LoginController
 * @description: 用户登录相关接口
 * @date 2024年11月20日
 * @version: 1.0.0
 */
@RestController
@RequiredArgsConstructor
public class LoginController {

    private final RedisOperator<String> redisOperator;

    private final IOauth2BasicUserService basicUserService;

    private final RegisteredClientRepository registeredClientRepository;

    private final OAuth2AuthorizationConsentService authorizationConsentService;

    @GetMapping("/api/v1/getSmsCaptcha")
    public Result<String> getSmsCaptcha(@RequestParam(name="phone") String phone) {
        // 示例项目，固定1234
        String smsCaptcha = "1234";
        // 存入缓存中，5分钟后过期
        redisOperator.set((SMS_CAPTCHA_PREFIX_KEY + phone), smsCaptcha, CAPTCHA_TIMEOUT_SECONDS);
        return Result.success("获取短信验证码成功.", smsCaptcha);
    }

    @GetMapping("/api/v1/getCaptcha")
    public Result<CaptchaResult> getCaptcha() {
        // 使用huTool-captcha生成图形验证码
        // 定义图形验证码的长、宽、验证码字符数、干扰线宽度
        ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(150, 40, 4, 2);
        // 生成一个唯一id
        long id = IdWorker.getId();
        // 存入缓存中，5分钟后过期
        redisOperator.set((IMAGE_CAPTCHA_PREFIX_KEY + id), captcha.getCode(), CAPTCHA_TIMEOUT_SECONDS);
        return Result.success("获取验证码成功.", new CaptchaResult(String.valueOf(id), captcha.getCode(),
                captcha.getImageBase64Data()));
    }

    /**
     * 处理用户信息请求的接口。
     * 该接口通过HTTP POST方法接收请求，并返回当前登录用户的基本信息。
     *
     * @return Result<Oauth2UserinfoResult> 包含当前登录用户信息的Result对象。
     *         如果操作成功，Result对象将包含Oauth2UserinfoResult类型的数据；
     *         如果操作失败，Result对象将包含错误信息。
     */
    @PostMapping("/api/v1/user")
    public Result<Oauth2UserinfoResult> user() {
        // 调用basicUserService的getLoginUserInfo方法获取当前登录用户的信息，并将其包装在Result对象中返回
        return Result.success(basicUserService.getLoginUserInfo());
    }

    /**
     * 获取OAuth2授权同意页面所需的参数。
     * 该函数处理GET请求，路径为"/oauth2/consent/parameters"，返回一个包含同意页面所需参数的Result对象。
     *
     * @param principal 当前认证用户的主体对象，用于获取用户信息。
     * @param clientId 客户端ID，标识请求授权的客户端应用。
     * @param scope 请求的权限范围，表示客户端请求的权限集合。
     * @param state 客户端传递的状态参数，用于防止CSRF攻击。
     * @param userCode 用户代码，可选参数，用于设备授权流程。
     * @return 返回一个Result对象，其中包含同意页面所需的参数Map。
     */
    @ResponseBody
    @GetMapping(value = "/api/v1/oauth2/consent/parameters")
    public Result<Map<String, Object>> consentParameters(Principal principal,
                                                         @RequestParam(OAuth2ParameterNames.CLIENT_ID) String clientId,
                                                         @RequestParam(OAuth2ParameterNames.SCOPE) String scope,
                                                         @RequestParam(OAuth2ParameterNames.STATE) String state,
                                                         @RequestParam(name = OAuth2ParameterNames.USER_CODE,
                                                                 required = false) String userCode) {

        // 调用getConsentParameters方法，获取同意页面所需的参数
        Map<String, Object> consentParameters = getConsentParameters(scope, state, clientId, userCode, principal);

        // 返回包含同意页面参数的Result对象
        return Result.success(consentParameters);
    }

    /**
     * 获取OAuth2授权同意请求的参数。
     *
     * 该函数用于处理OAuth2授权请求，生成包含客户端信息、授权范围、状态等参数的Map对象。
     * 函数会过滤掉已经授权的范围，并区分出需要新授权的范围和已经授权的范围。
     *
     * @param scope 请求的授权范围，多个范围以空格分隔。
     * @param state OAuth2授权请求的状态参数，用于防止CSRF攻击。
     * @param clientId 客户端的唯一标识符。
     * @param userCode 用户设备授权码，用于设备授权流程。
     * @param principal 当前认证的用户主体，包含用户的基本信息。
     * @return 包含授权请求参数的Map对象，键值对包括客户端ID、客户端名称、状态、授权范围等。
     */
    private Map<String, Object> getConsentParameters(String scope,
                                                     String state,
                                                     String clientId,
                                                     String userCode,
                                                     Principal principal) {
        // 初始化需要新授权的范围和已经授权的范围
        Set<String> scopesToApprove = new HashSet<>();
        Set<String> previouslyApprovedScopes = new HashSet<>();

        // 根据客户端ID查找注册的客户端信息
        RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId);
        if (registeredClient == null) {
            throw new RuntimeException("客户端不存在");
        }

        // 查找当前用户对该客户端的已有授权同意记录
        OAuth2AuthorizationConsent currentAuthorizationConsent =
                this.authorizationConsentService.findById(registeredClient.getId(), principal.getName());
        Set<String> authorizedScopes;
        if (currentAuthorizationConsent != null) {
            authorizedScopes = currentAuthorizationConsent.getScopes();
        } else {
            authorizedScopes = Collections.emptySet();
        }

        // 遍历请求的授权范围，区分出需要新授权的范围和已经授权的范围
        for (String requestedScope : StringUtils.delimitedListToStringArray(scope, " ")) {
            if (OidcScopes.OPENID.equals(requestedScope)) {
                continue; // 忽略OpenID Connect的默认范围
            }
            if (authorizedScopes.contains(requestedScope)) {
                previouslyApprovedScopes.add(requestedScope); // 已经授权的范围
            } else {
                scopesToApprove.add(requestedScope); // 需要新授权的范围
            }
        }

        // 构建包含授权请求参数的Map对象
        Map<String, Object> parameters = new HashMap<>(7);
        parameters.put("clientId", registeredClient.getClientId());
        parameters.put("clientName", registeredClient.getClientName());
        parameters.put("state", state);
        parameters.put("scopes", withDescription(scopesToApprove));
        parameters.put("previouslyApprovedScopes", withDescription(previouslyApprovedScopes));
        parameters.put("principalName", principal.getName());
        parameters.put("userCode", userCode);
        if (StringUtils.hasText(userCode)) {
            parameters.put("requestURI", "/oauth2/device_verification"); // 设备授权流程的请求URI
        } else {
            parameters.put("requestURI", "/oauth2/authorize"); // 标准授权流程的请求URI
        }

        return parameters;
    }

    /**
     * 根据给定的作用域字符串集合，创建带有描述的作用域对象集合
     * 此方法主要用于将一组作用域字符串转换为相应的带有描述的Scope对象，
     * 以便在后续处理中可以更方便地管理和使用这些作用域信息
     *
     * @param scopes 一组作用域字符串，代表不同的权限或访问范围
     * @return 返回一个Set集合，包含根据输入作用域创建的ScopeWithDescription对象
     */
    private static Set<LoginController.ScopeWithDescription> withDescription(Set<String> scopes) {
        Set<LoginController.ScopeWithDescription> scopeWithDescriptions = new HashSet<>();
        for (String scope : scopes) {
            scopeWithDescriptions.add(new LoginController.ScopeWithDescription(scope));
        }
        return scopeWithDescriptions;
    }

    /**
     * ScopeWithDescription 类用于封装权限范围（scope）及其对应的描述信息。
     * 该类通过静态初始化块预定义了一些常见的权限范围及其描述，并提供默认描述信息。
     */
    @Data
    public static class ScopeWithDescription {
        /**
         * 默认描述信息，当给定的权限范围未在预定义列表中找到时使用。
         */
        private static final String DEFAULT_DESCRIPTION = "UNKNOWN SCOPE - We cannot provide information about this " +
                "permission, use caution when granting this.";

        /**
         * 权限范围与其描述信息的映射表。
         * 通过静态初始化块预定义了一些常见的权限范围及其描述。
         */
        private static final Map<String, String> scopeDescriptions = new HashMap<>();

        // 静态初始化块，用于预定义权限范围及其描述信息
        static {
            scopeDescriptions.put(
                    OidcScopes.PROFILE,
                    "This application will be able to read your profile information."
            );
            scopeDescriptions.put(
                    "message.read",
                    "This application will be able to read your message."
            );
            scopeDescriptions.put(
                    "message.write",
                    "This application will be able to add new messages. It will also be able to edit and delete " +
                            "existing messages."
            );
            scopeDescriptions.put(
                    "other.scope",
                    "This is another scope example of a scope description."
            );
        }

        /**
         * 权限范围名称。
         */
        public final String scope;

        /**
         * 权限范围的描述信息。
         */
        public final String description;

        /**
         * 构造函数，根据给定的权限范围初始化 ScopeWithDescription 对象。
         * 如果权限范围在预定义的映射表中存在，则使用对应的描述信息；否则使用默认描述信息。
         *
         * @param scope 权限范围名称
         */
        ScopeWithDescription(String scope) {
            this.scope = scope;
            this.description = scopeDescriptions.getOrDefault(scope, DEFAULT_DESCRIPTION);
        }
    }

}
